/**
 * @file BG_estimate.cpp
 * @author enemy1205 (enemy1205@qq.com)
 * @date 2021-08-30
 */
#include "BG_estimate.h"
/**
 * @brief 计算vector中位数
 * @param elements 传入像素值数组
 * @return
 */
int Frame_difference::computeMedian(vector<int> &elements) {
    nth_element(elements.begin(), elements.begin() + elements.size() / 2, elements.end());
//    sort(elements.begin(),elements.end());
    return elements[elements.size() / 2];
}

/**
 * @brief 构造检测器
 * @param vec 传入像素值数组
 */
Frame_difference::Frame_difference(vector<Mat> vec) {
    // Note: Expects the image to be CV_8UC3
    Mat compute_median(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0));
    for (int row = 0; row < vec[0].rows; row++) {
        for (int col = 0; col < vec[0].cols; col++) {
            vector<int> elements_B;
            vector<int> elements_G;
            vector<int> elements_R;

            for (int imgNumber = 0; imgNumber < vec.size(); imgNumber++) {
                int B = vec[imgNumber].ptr<Vec3b>(row)[col][0];
                int G = vec[imgNumber].ptr<Vec3b>(row)[col][1];
                int R = vec[imgNumber].ptr<Vec3b>(row)[col][2];

                elements_B.push_back(B);
                elements_G.push_back(G);
                elements_R.push_back(R);
            }

            (compute_median).ptr<Vec3b>(row)[col][0] = computeMedian(elements_B);
            (compute_median).ptr<Vec3b>(row)[col][1] = computeMedian(elements_G);
            (compute_median).ptr<Vec3b>(row)[col][2] = computeMedian(elements_R);
        }
    }
    result = compute_median;
}


/**
 * @brief 传统帧差法分离前背景,需要大量帧算出背景，且准确率不高
 */
void BG_estimate::run() {
    VideoCapture cap(video_file);
    if (!cap.isOpened())
        cout << "Error opening video file\n";
    //创建引擎
    random_device rd;// Non-determinstic seed source
    default_random_engine generator{rd()};
    //创建取值范围
    uniform_int_distribution<int> distribution(0,cap.get(CAP_PROP_FRAME_COUNT));
    vector<Mat> frames;
    Mat frame;
    for (int i = 0; i < 100; i++) {
        int fid = distribution(generator);
        cap.set(CAP_PROP_POS_FRAMES, fid);
        cap.read(frame);
        if (frame.empty())
            continue;
//        imshow("1", frame);
//        waitKey(0);
//        destroyWindow("1");
        frames.push_back(frame);
    }
    Frame_difference FD(frames);
    // Display median frame
    imshow("frame", FD.Get_compute_median());
    waitKey(0);

}

/**
 * @breif 构造函数
 * @param str 视频路径
 */
BG_estimate::BG_estimate(string str) {
    video_file = str;
}

/**
 * @brief 调用opencv自带的api
 * @note MOG2综合最优
 */
void BG_estimate::api_run() {
    //opencv api BackgroundSubtractorMOG2
    VideoCapture capture(video_file);
    Mat frame, image, foreGround, backGround, fgMask;
    // Ptr<BackgroundSubtractorMOG2> pBgmodel = createBackgroundSubtractorMOG2(200,60, false);
   Ptr<bgsegm::BackgroundSubtractorCNT> pBgmodel = bgsegm::createBackgroundSubtractorCNT(20, true,600, true);
//    Ptr<BackgroundSubtractorKNN>  pBgmodel = createBackgroundSubtractorKNN(200,60, false);
//    pBgmodel->setShadowValue(false);
//    pBgmodel->setVarThreshold(70);//此处两行可由上述初始化时设置
    if (!capture.isOpened())
    {
        cout << "open videp eror!" << endl;
    }
    while (true)
    {
        //frame是原始帧
        capture >> frame;
        if (frame.empty())
            break;
        //缩小为原来四分之一，加快处理速度
        resize(frame, image, Size(frame.cols / 2, frame.rows / 2), INTER_LINEAR);
        if (foreGround.empty())
            foreGround.create(image.size() , image.type());
        //得到前景图像，是黑白灰 3种灰度值的图
        pBgmodel->apply(image, fgMask);
        // 下面是根据前景图的操作，和原图像融合得到有纹理的前景图
//        GaussianBlur(fgMask, fgMask, Size(3, 3) , 0);
//        threshold(fgMask , fgMask, 35, 255, THRESH_BINARY);
        Mat element = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
        morphologyEx(fgMask, fgMask ,MORPH_CLOSE, element);
        // 将foreGraound 所有像素置为0
        foreGround = Scalar::all(0);
        //fgMask对应点像素值为255则 foreGround像素为image里的像素，为0则直接为0
        image.copyTo(foreGround, fgMask);
        pBgmodel->getBackgroundImage(backGround);
        imshow("frame", frame);
//        imshow("backGround", backGround);
        imshow("foreGround", foreGround);
        imshow("fgMask", fgMask);

        char key = waitKey(100);
        if (key == 27)
            break;
    }
}